ποΈGitΠ―ΡΠ°ποΈ
Node / meshtastic / Meshtastic-Android / files / feature / firmware / src / commonMain / kotlin / org / meshtastic / feature / firmware / MaintenanceUf2.kt
Displaying Raw β’ Download
feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/MaintenanceUf2.kt bd2863243bab6eb213401d949839a2bc74dde7e2 (bd286324) Text, 13.79 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.firmware
Tff7b72import T7ee787org.meshtastic.core.model.DeviceHardware
Tff7b72import T7ee787org.meshtastic.core.model.EraseImageEntry
Tff7b72import T7ee787org.meshtastic.core.model.MaintenanceUf2Manifest
Tff7b72import T7ee787org.meshtastic.core.model.SoftDeviceVariant
Tff7b72import T7ee787org.meshtastic.core.network.HttpClientDefaults
T8b949e/**
* A pinned, content-verified UF2 image used by a maintenance flow (factory erase, bootloader upgrade).
*
* The URL/fileName/sha256 come from [MaintenanceUf2Manifest] (fetched from `resource/maintenanceUf2`, seeded from a
* bundled asset β see `MaintenanceUf2Repository`) rather than being hardcoded here, so a new OTAFIX release or a
* changed erase-image digest ships without an app release. Verifying [sha256] before any write is what makes that safe:
* this class is the download-time gate against a corrupted transfer, independent of how the manifest naming this image
* was itself fetched.
*
* @property expectedFirstTargetAddress For nRF erase images, the flash address the UF2's first block writes to. Checked
* against the resolved [SoftDeviceVariant] before the image is offered, because a swapped URL/digest row is the one
* authoring mistake a digest alone cannot catch β and the mistake that corrupts a SoftDevice. Null when the image's
* address carries no such invariant (RP2040, bootloader self-updates).
*/
Tff7b72internal Tff7b72data Tff7b72class T56d364MaintenanceUf2Tb4b4b4(
Tff7b72val Te6edf3urlTb4b4b4: Tffa657StringTb4b4b4,
Tff7b72val Te6edf3fileNameTb4b4b4: Tffa657StringTb4b4b4,
Tff7b72val Te6edf3sha256Tb4b4b4: Tffa657StringTb4b4b4,
Tff7b72val Te6edf3expectedFirstTargetAddressTb4b4b4: Tffa657Long? Tff7b72= Tff7b72nullTb4b4b4,
Tb4b4b4) Tb4b4b4{
Tff7b72init Tb4b4b4{
T8b949e// downloadFile interpolates fileName straight into a temp path. The mapping boundary already refuses unsafe
T8b949e// names (see SAFE_UF2_FILE_NAME) so this never fires in practice; it stays as defence-in-depth for values
T8b949e// constructed directly, and is deliberately the *same* rule so there is one definition of "safe".
Te6edf3requireTb4b4b4(Te6edf3SAFE_UF2_FILE_NAMETb4b4b4.Te6edf3matchesTb4b4b4(Te6edf3fileNameTb4b4b4)Tb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffUnsafe maintenance UF2 filename: Tffd700$Te6edf3fileNameTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e/**
* The only shape a manifest-supplied UF2 file name may take: a plain `<name>.uf2` of unreserved characters.
*
* Every name reaching [MaintenanceUf2] is now a string from `resource/maintenanceUf2` rather than a compile-time
* constant, so it is validated at the mapping boundary and a rejected row resolves to `null` β refusing that one image
* instead of throwing out of a resolver whose callers all document a nullable result. An allowlist rather than a
* traversal blacklist: excluding separators outright makes `..` inert, and nothing legitimate falls outside it.
*/
Tff7b72private Tff7b72val Te6edf3SAFE_UF2_FILE_NAME Tff7b72= Te6edf3RegexTb4b4b4(Ta5d6ff"""Ta5d6ff[A-Za-z0-9._-]+Ta5d6ff\Ta5d6ff.uf2Ta5d6ff"""Tb4b4b4)
T8b949e/**
* Factory-erase images are now vendored and served by `meshtastic/api` (`resource/maintenanceUf2/asset/<fileName>`)
* rather than a commit-pinned URL into `meshtastic/web-flasher`'s `public/uf2/` β see `data/maintenanceUf2.json` in
* that repo.
*
* `null` when the row names an unsafe file: a refusal of that image, never a crash. One malformed row must not take
* down the whole firmware screen.
*/
Tff7b72private Tff7b72fun Te6edf3EraseImageEntryTb4b4b4.Td2a8fftoMaintenanceUf2OrNullTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3MaintenanceUf2? Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3SAFE_UF2_FILE_NAMETb4b4b4.Te6edf3matchesTb4b4b4(Te6edf3fileNameTb4b4b4)Tb4b4b4) Tff7b72return Tff7b72null
Tff7b72return Te6edf3MaintenanceUf2Tb4b4b4(
Te6edf3url Tff7b72= Ta5d6ff"Tffd700${Te6edf3HttpClientDefaultsTb4b4b4.Te6edf3API_BASE_URLTffd700}Ta5d6ffresource/maintenanceUf2/asset/Tffd700$Te6edf3fileNameTa5d6ff"Tb4b4b4,
Te6edf3fileName Tff7b72= Te6edf3fileNameTb4b4b4,
Te6edf3sha256 Tff7b72= Te6edf3sha256Tb4b4b4,
Te6edf3expectedFirstTargetAddress Tff7b72= Te6edf3expectedFirstTargetAddressTb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e/**
* OTAFIX bootloader self-update images stay hosted on `Adafruit_nRF52_Bootloader_OTAFIX`'s own GitHub releases β only
* [MaintenanceUf2Manifest.otafixBase]/[MaintenanceUf2Manifest.otafixReleaseTag] (and the per-board digest) now come
* from the fetched manifest instead of being hardcoded.
*
* `null` on the same terms as [toMaintenanceUf2OrNull]. The name is composed from two manifest-supplied strings
* ([otafixBoardSlug] and [MaintenanceUf2Manifest.otafixReleaseTag]), so it is validated after composition.
*/
Tff7b72private Tff7b72fun Te6edf3MaintenanceUf2ManifestTb4b4b4.Td2a8ffotafixAssetOrNullTb4b4b4(Te6edf3otafixBoardSlugTb4b4b4: Tffa657StringTb4b4b4, Te6edf3sha256Tb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3MaintenanceUf2? Tb4b4b4{
Tff7b72val Te6edf3name Tff7b72= Ta5d6ff"Ta5d6ffupdate-Tffd700${Te6edf3otafixBoardSlugTffd700}Ta5d6ff_bootloader-Tffd700${Te6edf3otafixReleaseTagTffd700}Ta5d6ff_nosd.uf2Ta5d6ff"
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3SAFE_UF2_FILE_NAMETb4b4b4.Te6edf3matchesTb4b4b4(Te6edf3nameTb4b4b4)Tb4b4b4) Tff7b72return Tff7b72null
Tff7b72return Te6edf3MaintenanceUf2Tb4b4b4(Te6edf3url Tff7b72= Ta5d6ff"Tffd700$Te6edf3otafixBaseTa5d6ff/Tffd700$Te6edf3nameTa5d6ff"Tb4b4b4, Te6edf3fileName Tff7b72= Te6edf3nameTb4b4b4, Te6edf3sha256 Tff7b72= Te6edf3sha256Tb4b4b4)
Tb4b4b4}
T8b949e/** [SoftDeviceVariant.fromWire]'s own input strings β the key space [MaintenanceUf2EraseSet.nrf52] is indexed by. */
Tff7b72private Tff7b72val Te6edf3SoftDeviceVariantTb4b4b4.Te6edf3wireValueTb4b4b4: Tffa657String
Tff7b72getTb4b4b4(Tb4b4b4) Tff7b72=
Tff7b72when Tb4b4b4(Tff7b72thisTb4b4b4) Tb4b4b4{
Te6edf3SoftDeviceVariantTb4b4b4.Te6edf3S140_6_1_1 Tff7b72-Tff7b72> Ta5d6ff"Ta5d6ff6.1.1Ta5d6ff"
Te6edf3SoftDeviceVariantTb4b4b4.Te6edf3S140_7_3_0 Tff7b72-Tff7b72> Ta5d6ff"Ta5d6ff7.3.0Ta5d6ff"
Tb4b4b4}
T8b949e/**
* The factory-erase image for [hardware] given [manifest], or `null` when none can be resolved safely.
*
* `null` when [manifest] carries no `erase` set at all (never fetched/seeded yet β fail closed, same as an unresolved
* [DeviceHardware.softDeviceVariant]), when the matching row names an unsafe file, or β for nRF52840 β without a
* resolved [DeviceHardware.softDeviceVariant]: the two images are linked for different application start addresses, and
* the UF2 bootloader's write guard begins at `MBR_SIZE`, so the wrong one erases a SoftDevice page. There is
* deliberately no default branch.
*/
Tff7b72internal Tff7b72fun Td2a8fferaseUf2ForTb4b4b4(Te6edf3manifestTb4b4b4: Te6edf3MaintenanceUf2ManifestTb4b4b4, Te6edf3hardwareTb4b4b4: Te6edf3DeviceHardwareTb4b4b4)Tb4b4b4: Te6edf3MaintenanceUf2? Tb4b4b4{
Tff7b72val Te6edf3erase Tff7b72= Te6edf3manifestTb4b4b4.Te6edf3erase Tff7b72?: Tff7b72return Tff7b72null
Tff7b72return Tff7b72when Tb4b4b4{
Te6edf3hardwareTb4b4b4.Te6edf3isRp2040Arc Tff7b72-Tff7b72> Te6edf3eraseTb4b4b4.Te6edf3rp2040Tb4b4b4.Te6edf3toMaintenanceUf2OrNullTb4b4b4(Tb4b4b4)
Te6edf3hardwareTb4b4b4.Te6edf3isNrf52Arc Tff7b72-Tff7b72> Te6edf3hardwareTb4b4b4.Te6edf3softDeviceVariantTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3eraseTb4b4b4.Te6edf3nrf52Tff7b72[Tffa657itTb4b4b4.Te6edf3wireValueTff7b72]Tff7b72?.Te6edf3toMaintenanceUf2OrNullTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72else Tff7b72-Tff7b72> Tff7b72null
Tb4b4b4}
Tb4b4b4}
T8b949e/**
* True when [manifest] lists OTAFIX support for [platformioTarget]'s product. UX gate only β see
* [MaintenanceUf2Manifest.otafixSupportedTargets]'s own doc in the source data.
*/
Tff7b72internal Tff7b72fun Td2a8ffotafixSupportsTargetTb4b4b4(Te6edf3manifestTb4b4b4: Te6edf3MaintenanceUf2ManifestTb4b4b4, Te6edf3platformioTargetTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72=
Te6edf3platformioTarget Tff7b72in Te6edf3manifestTb4b4b4.Te6edf3otafixSupportedTargets
T8b949e/**
* The OTAFIX image matching the [boardId] a device reported in its `INFO_UF2.TXT`, or `null` when unrecognized.
*
* `null` refuses the upgrade. That is the correct outcome even for a board OTAFIX supports: an unrecognized Board-ID
* means the installed bootloader is not one we have a verified pairing for, and writing a bootloader built for other
* hardware is unrecoverable without SWD.
*/
Tff7b72internal Tff7b72fun Td2a8ffotafixUf2ForBoardIdTb4b4b4(Te6edf3manifestTb4b4b4: Te6edf3MaintenanceUf2ManifestTb4b4b4, Te6edf3boardIdTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3MaintenanceUf2? Tff7b72=
Te6edf3manifestTb4b4b4.Te6edf3otafixByBoardIdTff7b72[Te6edf3boardIdTb4b4b4.Te6edf3trimTb4b4b4(Tb4b4b4)Tff7b72]Tff7b72?.Te6edf3let Tb4b4b4{
Te6edf3manifestTb4b4b4.Te6edf3otafixAssetOrNullTb4b4b4(Te6edf3otafixBoardSlug Tff7b72= Tffa657itTb4b4b4.Te6edf3otafixBoardSlugTb4b4b4, Te6edf3sha256 Tff7b72= Tffa657itTb4b4b4.Te6edf3sha256Tb4b4b4)
Tb4b4b4}
T8b949e/**
* Extracts the `Board-ID:` value from the contents of a UF2 bootloader's `INFO_UF2.TXT`.
*
* Format is fixed by `ghostfat.c`: `UF2 Bootloader <ver>` / `Model: <name>` / `Board-ID: <id>` / `Date: <date>`,
* CRLF-separated. Returns `null` when the line is absent, which means the volume is not an Adafruit-family UF2
* bootloader drive β itself a reason to refuse a destructive write.
*/
Tff7b72internal Tff7b72fun Td2a8ffparseUf2BoardIdTb4b4b4(Te6edf3infoUf2TextTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657String? Tff7b72= Te6edf3infoUf2Text
Tb4b4b4.Te6edf3lineSequenceTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3firstOrNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3trimStartTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3startsWithTb4b4b4(Te6edf3UF2_BOARD_ID_PREFIXTb4b4b4, Te6edf3ignoreCase Tff7b72= Tff7b72trueTb4b4b4) Tb4b4b4}
Tff7b72?.Te6edf3substringAfterTb4b4b4(Ta5d6ff':'Tb4b4b4)
Tff7b72?.Te6edf3trimTb4b4b4(Tb4b4b4)
Tff7b72?.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tb4b4b4}
T8b949e/**
* Extracts the installed SoftDevice from the contents of a UF2 bootloader's `INFO_UF2.TXT`.
*
* `uf2_init()` appends this line at boot from `SD_ID_GET(MBR_SIZE)`/`SD_VERSION_GET(MBR_SIZE)` β i.e. read out of the
* MBR's registers β formatted as `SoftDevice: S<id> <major>.<minor>.<patch>`. Present in upstream Adafruit and in
* OTAFIX, and verified on a stock Seeed bootloader (`SoftDevice: S140 7.3.0`).
*
* This is the **authoritative** answer to the question the manifest only estimates: not what the firmware was built
* against, but which SoftDevice is actually in flash. Returns `null` when the line is absent (very old bootloader),
* when no SoftDevice is installed, or when the id/version is not one we ship an erase image for.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4) T8b949e// guard clauses; an unparseable line must yield null rather than a guess
Tff7b72internal Tff7b72fun Td2a8ffparseUf2SoftDeviceTb4b4b4(Te6edf3infoUf2TextTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3SoftDeviceVariant? Tb4b4b4{
Tff7b72val Te6edf3value Tff7b72=
Te6edf3infoUf2Text
Tb4b4b4.Te6edf3lineSequenceTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3firstOrNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3trimStartTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3startsWithTb4b4b4(Te6edf3UF2_SOFTDEVICE_PREFIXTb4b4b4, Te6edf3ignoreCase Tff7b72= Tff7b72trueTb4b4b4) Tb4b4b4}
Tff7b72?.Te6edf3substringAfterTb4b4b4(Ta5d6ff':'Tb4b4b4)
Tff7b72?.Te6edf3trimTb4b4b4(Tb4b4b4) Tff7b72?: Tff7b72return Tff7b72null
Tff7b72val Te6edf3parts Tff7b72= Te6edf3valueTb4b4b4.Te6edf3splitTb4b4b4(Ta5d6ff' 'Tb4b4b4)Tb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotBlankTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3partsTb4b4b4.Te6edf3size Tff7b72< T79c0ff2 Tff7b72|Tff7b72| Tff7b72!Te6edf3partsTff7b72[T79c0ff0Tff7b72]Tb4b4b4.Te6edf3equalsTb4b4b4(Te6edf3SUPPORTED_SOFTDEVICE_IDTb4b4b4, Te6edf3ignoreCase Tff7b72= Tff7b72trueTb4b4b4)Tb4b4b4) Tff7b72return Tff7b72null
Tff7b72return Te6edf3SoftDeviceVariantTb4b4b4.Te6edf3fromWireTb4b4b4(Te6edf3partsTff7b72[T79c0ff1Tff7b72]Tb4b4b4)
Tb4b4b4}
T8b949e/**
* Which erase image [variant] needs, from [manifest]. `null` when [manifest] carries no `erase` set at all (never
* fetched/seeded β fail closed), when this specific variant's row is missing from `erase.nrf52` (a malformed or partial
* manifest), or when that row names an unsafe file β never a guess at a substitute image.
*/
Tff7b72internal Tff7b72fun Td2a8fferaseUf2ForVariantTb4b4b4(Te6edf3manifestTb4b4b4: Te6edf3MaintenanceUf2ManifestTb4b4b4, Te6edf3variantTb4b4b4: Te6edf3SoftDeviceVariantTb4b4b4)Tb4b4b4: Te6edf3MaintenanceUf2? Tb4b4b4{
Tff7b72val Te6edf3erase Tff7b72= Te6edf3manifestTb4b4b4.Te6edf3erase Tff7b72?: Tff7b72return Tff7b72null
Tff7b72return Te6edf3eraseTb4b4b4.Te6edf3nrf52Tff7b72[Te6edf3variantTb4b4b4.Te6edf3wireValueTff7b72]Tff7b72?.Te6edf3toMaintenanceUf2OrNullTb4b4b4(Tb4b4b4)
Tb4b4b4}
T8b949e/** Outcome of reconciling the SoftDevice the drive reports against the manifest's pre-flight hint. */
Tff7b72internal Tff7b72sealed Tff7b72interface T56d364EraseImageResolution Tb4b4b4{
T8b949e/** Safe to write [asset]; [variant] is the SoftDevice it is linked for. */
Tff7b72data Tff7b72class T56d364ResolvedTb4b4b4(Tff7b72val Te6edf3assetTb4b4b4: Te6edf3MaintenanceUf2Tb4b4b4, Tff7b72val Te6edf3variantTb4b4b4: Te6edf3SoftDeviceVariantTb4b4b4) Tb4b4b4: Te6edf3EraseImageResolution
T8b949e/**
* The drive and the manifest's pre-flight hint disagree. Always a refusal: one of the two is wrong and we cannot
* tell which, and guessing writes an erase image into a SoftDevice. Also the signal that a map row needs
* correcting.
*/
Tff7b72data Tff7b72class T56d364ConflictTb4b4b4(Tff7b72val Te6edf3reportedTb4b4b4: Te6edf3SoftDeviceVariantTb4b4b4, Tff7b72val Te6edf3mappedTb4b4b4: Te6edf3SoftDeviceVariantTb4b4b4) Tb4b4b4: Te6edf3EraseImageResolution
T8b949e/** Neither source produced a variant, or the manifest has no erase images to offer at all. */
Tff7b72data Tff7b72object T56d364Unresolved Tb4b4b4: Te6edf3EraseImageResolution
Tb4b4b4}
T8b949e/**
* Picks the nRF erase image, preferring what the device reports over what [manifest]'s pre-flight hint predicted.
*
* The hint ([DeviceHardware.softDeviceVariant], itself derived from the bootloader-quirks catalog) decides whether the
* action is offered before any drive is mounted. Once the drive is readable its own report wins, because it comes from
* the MBR rather than from a hand-authored table. A disagreement refuses rather than picking a side.
*/
Tff7b72internal Tff7b72fun Td2a8ffresolveNrfEraseImageTb4b4b4(
Te6edf3manifestTb4b4b4: Te6edf3MaintenanceUf2ManifestTb4b4b4,
Te6edf3mappedTb4b4b4: Te6edf3SoftDeviceVariant?Tb4b4b4,
Te6edf3reportedFromDriveTb4b4b4: Te6edf3SoftDeviceVariant?Tb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3EraseImageResolution Tff7b72= Tff7b72when Tb4b4b4{
Te6edf3reportedFromDrive Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3mapped Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3reportedFromDrive Tff7b72!Tff7b72= Te6edf3mapped Tff7b72-Tff7b72>
Te6edf3EraseImageResolutionTb4b4b4.Te6edf3ConflictTb4b4b4(Te6edf3reported Tff7b72= Te6edf3reportedFromDriveTb4b4b4, Te6edf3mapped Tff7b72= Te6edf3mappedTb4b4b4)
Te6edf3reportedFromDrive Tff7b72!Tff7b72= Tff7b72null Tff7b72-Tff7b72>
Te6edf3eraseUf2ForVariantTb4b4b4(Te6edf3manifestTb4b4b4, Te6edf3reportedFromDriveTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{
Te6edf3EraseImageResolutionTb4b4b4.Te6edf3ResolvedTb4b4b4(Tffa657itTb4b4b4, Te6edf3reportedFromDriveTb4b4b4)
Tb4b4b4} Tff7b72?: Te6edf3EraseImageResolutionTb4b4b4.Te6edf3Unresolved
T8b949e// No SoftDevice line: a bootloader older than the uf2_init that emits it. Fall back to the pre-flight hint.
Te6edf3mapped Tff7b72!Tff7b72= Tff7b72null Tff7b72-Tff7b72>
Te6edf3eraseUf2ForVariantTb4b4b4(Te6edf3manifestTb4b4b4, Te6edf3mappedTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3EraseImageResolutionTb4b4b4.Te6edf3ResolvedTb4b4b4(Tffa657itTb4b4b4, Te6edf3mappedTb4b4b4) Tb4b4b4}
Tff7b72?: Te6edf3EraseImageResolutionTb4b4b4.Te6edf3Unresolved
Tff7b72else Tff7b72-Tff7b72> Te6edf3EraseImageResolutionTb4b4b4.Te6edf3Unresolved
Tb4b4b4}
T8b949e/** The file every Adafruit-family UF2 bootloader exposes on its mass-storage volume. */
Tff7b72internal Tff7b72const Tff7b72val Te6edf3INFO_UF2_FILE_NAME Tff7b72= Ta5d6ff"Ta5d6ffINFO_UF2.TXTTa5d6ff"
Tff7b72private Tff7b72const Tff7b72val Te6edf3UF2_BOARD_ID_PREFIX Tff7b72= Ta5d6ff"Ta5d6ffBoard-ID:Ta5d6ff"
Tff7b72private Tff7b72const Tff7b72val Te6edf3UF2_SOFTDEVICE_PREFIX Tff7b72= Ta5d6ff"Ta5d6ffSoftDevice:Ta5d6ff"
T8b949e/** All Meshtastic nRF52840 boards run the S140 SoftDevice; anything else is out of scope and refuses. */
Tff7b72private Tff7b72const Tff7b72val Te6edf3SUPPORTED_SOFTDEVICE_ID Tff7b72= Ta5d6ff"Ta5d6ffS140Ta5d6ff"
T8b949e/** UF2 block size, per the UF2 specification. */
Tff7b72internal Tff7b72const Tff7b72val Te6edf3UF2_BLOCK_BYTES Tff7b72= T79c0ff5T79c0ff1T79c0ff2
T8b949e/** Byte offset of `targetAddr` within a UF2 block header. */
Tff7b72internal Tff7b72const Tff7b72val Te6edf3UF2_TARGET_ADDR_OFFSET Tff7b72= T79c0ff1T79c0ff2
Tff7b72private Tff7b72const Tff7b72val Te6edf3UF2_MAGIC_START0 Tff7b72= T79c0ff0Te6edf3x0A324655
T8b949e/** Bytes in a little-endian 32-bit field, and the mask/shift used to reassemble one. */
Tff7b72private Tff7b72const Tff7b72val Te6edf3UINT32_BYTES Tff7b72= T79c0ff4
Tff7b72private Tff7b72const Tff7b72val Te6edf3BITS_PER_BYTE Tff7b72= T79c0ff8
Tff7b72private Tff7b72const Tff7b72val Te6edf3BYTE_MASK Tff7b72= T79c0ff0Te6edf3xFFL
T8b949e/**
* Reads the target flash address of the first UF2 block in [bytes], or `null` when the payload is not a UF2 image.
*
* Used to cross-check a pinned erase image against the resolved SoftDevice variant before it is written.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4) T8b949e// guard clauses over a binary header
Tff7b72internal Tff7b72fun Td2a8ffuf2FirstTargetAddressTb4b4b4(Te6edf3bytesTb4b4b4: Te6edf3ByteArrayTb4b4b4)Tb4b4b4: Tffa657Long? Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3bytesTb4b4b4.Te6edf3size Tff7b72< Te6edf3UF2_BLOCK_BYTESTb4b4b4) Tff7b72return Tff7b72null
Tff7b72if Tb4b4b4(Te6edf3readLittleEndianUInt32Tb4b4b4(Te6edf3bytesTb4b4b4, T79c0ff0Tb4b4b4) Tff7b72!Tff7b72= Te6edf3UF2_MAGIC_START0Tb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72return Tff7b72null
Tff7b72return Te6edf3readLittleEndianUInt32Tb4b4b4(Te6edf3bytesTb4b4b4, Te6edf3UF2_TARGET_ADDR_OFFSETTb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffreadLittleEndianUInt32Tb4b4b4(Te6edf3bytesTb4b4b4: Te6edf3ByteArrayTb4b4b4, Te6edf3offsetTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Tffa657Long Tb4b4b4{
Tff7b72var Te6edf3value Tff7b72= T79c0ff0L
Tff7b72for Tb4b4b4(Te6edf3i Tff7b72in Te6edf3UINT32_BYTES Tff7b72- T79c0ff1 Te6edf3downTo T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3value Tff7b72= Tb4b4b4(Te6edf3value Te6edf3shl Te6edf3BITS_PER_BYTETb4b4b4) Te6edf3or Tb4b4b4(Te6edf3bytesTff7b72[Te6edf3offset Tff7b72+ Te6edf3iTff7b72]Tb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4) Te6edf3and Te6edf3BYTE_MASKTb4b4b4)
Tb4b4b4}
Tff7b72return Te6edf3value
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.09s